/*
 * Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sun.org.apache.xpath.internal.compiler;

import com.sun.org.apache.xalan.internal.res.XSLMessages;
import com.sun.org.apache.xml.internal.utils.ObjectVector;
import com.sun.org.apache.xpath.internal.patterns.NodeTest;
import com.sun.org.apache.xpath.internal.res.XPATHErrorResources;

/**
 * This class represents the data structure basics of the XPath
 * object.
 */
public class OpMap {

    /**
     * The current pattern string, for diagnostics purposes
     */
    protected String m_currentPattern;

    /**
     * Return the expression as a string for diagnostics.
     *
     * @return The expression string.
     */
    public String toString() {
        return m_currentPattern;
    }

    /**
     * Return the expression as a string for diagnostics.
     *
     * @return The expression string.
     */
    public String getPatternString() {
        return m_currentPattern;
    }

    /**
     * The starting size of the token queue.
     */
    static final int MAXTOKENQUEUESIZE = 500;

    /*
     * Amount to grow token queue when it becomes full
     */
    static final int BLOCKTOKENQUEUESIZE = 500;

    /**
     * TokenStack is the queue of used tokens. The current token is the token at the
     * end of the m_tokenQueue. The idea is that the queue can be marked and a sequence
     * of tokens can be reused.
     */
    ObjectVector m_tokenQueue = new ObjectVector(MAXTOKENQUEUESIZE, BLOCKTOKENQUEUESIZE);

    /**
     * Get the XPath as a list of tokens.
     *
     * @return ObjectVector of tokens.
     */
    public ObjectVector getTokenQueue() {
        return m_tokenQueue;
    }

    /**
     * Get the XPath as a list of tokens.
     *
     * @param pos index into token queue.
     * @return The token, normally a string.
     */
    public Object getToken(int pos) {
        return m_tokenQueue.elementAt(pos);
    }

    /**
     * The current size of the token queue.
     */
//  public int m_tokenQueueSize = 0;

    /**
     * Get size of the token queue.
     *
     * @return The size of the token queue.
     */
    public int getTokenQueueSize() {
        return m_tokenQueue.size();

    }

    /**
     * An operations map is used instead of a proper parse tree.  It contains
     * operations codes and indexes into the m_tokenQueue.
     * I use an array instead of a full parse tree in order to cut down
     * on the number of objects created.
     */
    OpMapVector m_opMap = null;

    /**
     * Get the opcode list that describes the XPath operations.  It contains
     * operations codes and indexes into the m_tokenQueue.
     * I use an array instead of a full parse tree in order to cut down
     * on the number of objects created.
     *
     * @return An IntVector that is the opcode list that describes the XPath operations.
     */
    public OpMapVector getOpMap() {
        return m_opMap;
    }

    // Position indexes

    /**
     * The length is always the opcode position + 1.
     * Length is always expressed as the opcode+length bytes,
     * so it is always 2 or greater.
     */
    public static final int MAPINDEX_LENGTH = 1;

    /**
     * Replace the large arrays
     * with a small array.
     */
    void shrink() {

        int n = m_opMap.elementAt(MAPINDEX_LENGTH);
        m_opMap.setToSize(n + 4);

        m_opMap.setElementAt(0, n);
        m_opMap.setElementAt(0, n + 1);
        m_opMap.setElementAt(0, n + 2);


        n = m_tokenQueue.size();
        m_tokenQueue.setToSize(n + 4);

        m_tokenQueue.setElementAt(null, n);
        m_tokenQueue.setElementAt(null, n + 1);
        m_tokenQueue.setElementAt(null, n + 2);
    }

    /**
     * Given an operation position, return the current op.
     *
     * @param opPos index into op map.
     * @return the op that corresponds to the opPos argument.
     */
    public int getOp(int opPos) {
        return m_opMap.elementAt(opPos);
    }

    /**
     * Set the op at index to the given int.
     *
     * @param opPos index into op map.
     * @param value Value to set
     */
    public void setOp(int opPos, int value) {
        m_opMap.setElementAt(value, opPos);
    }

    /**
     * Given an operation position, return the end position, i.e. the
     * beginning of the next operation.
     *
     * @param opPos An op position of an operation for which there is a size
     *              entry following.
     * @return position of next operation in m_opMap.
     */
    public int getNextOpPos(int opPos) {
        return opPos + m_opMap.elementAt(opPos + 1);
    }

    /**
     * Given a location step position, return the end position, i.e. the
     * beginning of the next step.
     *
     * @param opPos the position of a location step.
     * @return the position of the next location step.
     */
    public int getNextStepPos(int opPos) {

        int stepType = getOp(opPos);

        if ((stepType >= OpCodes.AXES_START_TYPES)
                && (stepType <= OpCodes.AXES_END_TYPES)) {
            return getNextOpPos(opPos);
        } else if ((stepType >= OpCodes.FIRST_NODESET_OP)
                && (stepType <= OpCodes.LAST_NODESET_OP)) {
            int newOpPos = getNextOpPos(opPos);

            while (OpCodes.OP_PREDICATE == getOp(newOpPos)) {
                newOpPos = getNextOpPos(newOpPos);
            }

            stepType = getOp(newOpPos);

            if (!((stepType >= OpCodes.AXES_START_TYPES)
                    && (stepType <= OpCodes.AXES_END_TYPES))) {
                return OpCodes.ENDOP;
            }

            return newOpPos;
        } else {
            throw new RuntimeException(
                    XSLMessages.createXPATHMessage(XPATHErrorResources.ER_UNKNOWN_STEP, new Object[]{String.valueOf(stepType)}));
            //"Programmer's assertion in getNextStepPos: unknown stepType: " + stepType);
        }
    }

    /**
     * Given an operation position, return the end position, i.e. the
     * beginning of the next operation.
     *
     * @param opMap The operations map.
     * @param opPos index to operation, for which there is a size entry following.
     * @return position of next operation in m_opMap.
     */
    public static int getNextOpPos(int[] opMap, int opPos) {
        return opPos + opMap[opPos + 1];
    }

    /**
     * Given an FROM_stepType position, return the position of the
     * first predicate, if there is one, or else this will point
     * to the end of the FROM_stepType.
     * Example:
     * int posOfPredicate = xpath.getNextOpPos(stepPos);
     * boolean hasPredicates =
     * OpCodes.OP_PREDICATE == xpath.getOp(posOfPredicate);
     *
     * @param opPos position of FROM_stepType op.
     * @return position of predicate in FROM_stepType structure.
     */
    public int getFirstPredicateOpPos(int opPos)
            throws javax.xml.transform.TransformerException {

        int stepType = m_opMap.elementAt(opPos);

        if ((stepType >= OpCodes.AXES_START_TYPES)
                && (stepType <= OpCodes.AXES_END_TYPES)) {
            return opPos + m_opMap.elementAt(opPos + 2);
        } else if ((stepType >= OpCodes.FIRST_NODESET_OP)
                && (stepType <= OpCodes.LAST_NODESET_OP)) {
            return opPos + m_opMap.elementAt(opPos + 1);
        } else if (-2 == stepType) {
            return -2;
        } else {
            error(com.sun.org.apache.xpath.internal.res.XPATHErrorResources.ER_UNKNOWN_OPCODE,
                    new Object[]{String.valueOf(stepType)});  //"ERROR! Unknown op code: "+m_opMap[opPos]);
            return -1;
        }
    }

    /**
     * Tell the user of an error, and probably throw an
     * exception.
     *
     * @param msg  An error msgkey that corresponds to one of the constants found
     *             in {@link com.sun.org.apache.xpath.internal.res.XPATHErrorResources}, which is
     *             a key for a format string.
     * @param args An array of arguments represented in the format string, which
     *             may be null.
     * @throws TransformerException if the current ErrorListoner determines to
     *                              throw an exception.
     */
    public void error(String msg, Object[] args) throws javax.xml.transform.TransformerException {

        java.lang.String fmsg = com.sun.org.apache.xalan.internal.res.XSLMessages.createXPATHMessage(msg, args);


        throw new javax.xml.transform.TransformerException(fmsg);
    }


    /**
     * Go to the first child of a given operation.
     *
     * @param opPos position of operation.
     * @return The position of the first child of the operation.
     */
    public static int getFirstChildPos(int opPos) {
        return opPos + 2;
    }

    /**
     * Get the length of an operation.
     *
     * @param opPos The position of the operation in the op map.
     * @return The size of the operation.
     */
    public int getArgLength(int opPos) {
        return m_opMap.elementAt(opPos + MAPINDEX_LENGTH);
    }

    /**
     * Given a location step, get the length of that step.
     *
     * @param opPos Position of location step in op map.
     * @return The length of the step.
     */
    public int getArgLengthOfStep(int opPos) {
        return m_opMap.elementAt(opPos + MAPINDEX_LENGTH + 1) - 3;
    }

    /**
     * Get the first child position of a given location step.
     *
     * @param opPos Position of location step in the location map.
     * @return The first child position of the step.
     */
    public static int getFirstChildPosOfStep(int opPos) {
        return opPos + 3;
    }

    /**
     * Get the test type of the step, i.e. NODETYPE_XXX value.
     *
     * @param opPosOfStep The position of the FROM_XXX step.
     * @return NODETYPE_XXX value.
     */
    public int getStepTestType(int opPosOfStep) {
        return m_opMap.elementAt(opPosOfStep + 3);  // skip past op, len, len without predicates
    }

    /**
     * Get the namespace of the step.
     *
     * @param opPosOfStep The position of the FROM_XXX step.
     * @return The step's namespace, NodeTest.WILD, or null for null namespace.
     */
    public String getStepNS(int opPosOfStep) {

        int argLenOfStep = getArgLengthOfStep(opPosOfStep);

        // System.out.println("getStepNS.argLenOfStep: "+argLenOfStep);
        if (argLenOfStep == 3) {
            int index = m_opMap.elementAt(opPosOfStep + 4);

            if (index >= 0)
                return (String) m_tokenQueue.elementAt(index);
            else if (OpCodes.ELEMWILDCARD == index)
                return NodeTest.WILD;
            else
                return null;
        } else
            return null;
    }

    /**
     * Get the local name of the step.
     *
     * @param opPosOfStep The position of the FROM_XXX step.
     * @return OpCodes.EMPTY, OpCodes.ELEMWILDCARD, or the local name.
     */
    public String getStepLocalName(int opPosOfStep) {

        int argLenOfStep = getArgLengthOfStep(opPosOfStep);

        // System.out.println("getStepLocalName.argLenOfStep: "+argLenOfStep);
        int index;

        switch (argLenOfStep) {
            case 0:
                index = OpCodes.EMPTY;
                break;
            case 1:
                index = OpCodes.ELEMWILDCARD;
                break;
            case 2:
                index = m_opMap.elementAt(opPosOfStep + 4);
                break;
            case 3:
                index = m_opMap.elementAt(opPosOfStep + 5);
                break;
            default:
                index = OpCodes.EMPTY;
                break;  // Should assert error
        }

        // int index = (argLenOfStep == 3) ? m_opMap[opPosOfStep+5]
        //                                  : ((argLenOfStep == 1) ? -3 : -2);
        if (index >= 0)
            return (String) m_tokenQueue.elementAt(index).toString();
        else if (OpCodes.ELEMWILDCARD == index)
            return NodeTest.WILD;
        else
            return null;
    }

}
